package servicex

import (
	"bytes"
	"encoding/base64"
	"errors"
	"fmt"
	"io/ioutil"
	"mime"
	"net/http"
	"net/smtp"
	"strings"
	"time"

	"zhonghui/console/warehouse/cfgx"
)

func init() {
	// 暂时不用
	// config.Fetch(config.CfgItem{Id: "mail", Group: "common"})
}

// SendMail 发送邮件
// fromName 指发件人别名，请避免使用特殊字符
// to 发件人
// cc 抄送
// title 标题
// body 正文
// fileUrl需要传递文件网址，如OSS文件地址，本方法会先将文件下载下来，然后再进行发送，会消耗一定时间，所以推荐传递OSS VPC内网文件地址，或异步发送
// fileTitle 文件名，如：发送表格的时候，文件名设置成 1.xlsx
func SendMail(fromName string, to, cc []string, title, body, fileUrl, fileTitle string) error {
	cfg := cfgx.Cfg().GetStringMapString("mail")

	// 校验
	if to == nil || len(to) == 0 {
		return errors.New("收件地址不可为空")
	}
	if cc == nil {
		cc = []string{}
	}
	if title == "" {
		return errors.New("标题不可为空")
	}

	mail := &SendMailCfg{user: cfg["user"], password: cfg["password"], host: cfg["host"], port: cfg["port"]}

	// 邮件基本信息
	message := Message{
		from:        cfg["user"],
		fromName:    fromName,
		to:          to,
		cc:          cc,
		bcc:         []string{},
		subject:     title,
		body:        body,
		contentType: "text/html;charset=utf-8",
	}

	// 附件
	if fileUrl != "" {
		message.attachment = Attachment{
			title:       fileTitle,
			name:        fileUrl,
			contentType: "application/octet-stream",
			withFile:    true,
		}
	}

	return mail.send(message)
}

// Mail 邮件接口
type Mail interface {
	Auth()
	send(message Message) error
}

// SendMailCfg 邮件认证结构体
type SendMailCfg struct {
	user     string
	password string
	host     string
	port     string
	auth     smtp.Auth
}

// Attachment 邮件附件结构体
type Attachment struct {
	title       string
	name        string
	contentType string
	withFile    bool
}

// Message 邮件内容结构体
type Message struct {
	from        string
	fromName    string
	to          []string
	cc          []string
	bcc         []string
	subject     string
	body        string
	contentType string
	attachment  Attachment
}

// Auth 邮件认证
func (mail *SendMailCfg) Auth() {
	mail.auth = LoginAuth(mail.user, mail.password)
}

// send 邮件发送
func (mail SendMailCfg) send(message Message) error {
	mail.Auth()

	buffer := bytes.NewBuffer(nil)
	boundary := "GoBoundary"

	mailHeader := make(map[string]string)

	// 处理发件人别名
	if message.fromName == "" {
		mailHeader["From"] = message.from
	} else {
		mailHeader["From"] = fmt.Sprintf("\"%s\" <%s>", message.fromName, message.from)
	}

	mailHeader["To"] = strings.Join(message.to, ";")
	mailHeader["Cc"] = strings.Join(message.cc, ";")
	mailHeader["Bcc"] = strings.Join(message.bcc, ";")
	mailHeader["Subject"] = message.subject
	mailHeader["Content-Type"] = "multipart/mixed;boundary=" + boundary
	mailHeader["Mime-Version"] = "1.0"
	mailHeader["Date"] = time.Now().String()
	mail.writeHeader(buffer, mailHeader)

	body := fmt.Sprintf("\r\n--%s\r\n", boundary)
	body += fmt.Sprintf("Content-Type:%s\r\n", message.contentType)
	body += fmt.Sprintf("\r\n%s\r\n", message.body)
	buffer.WriteString(body)

	if message.attachment.withFile {
		// 文件标题
		fileTitle := message.attachment.name
		if message.attachment.title != "" {
			fileTitle = message.attachment.title
		}

		// 开始拼接邮件正文
		attachment := fmt.Sprintf("\r\n--%s\r\n", boundary)
		attachment += "Content-Transfer-Encoding:base64\r\n"
		attachment += "Content-Disposition:attachment\r\n"
		attachment += fmt.Sprintf("Content-Type:%s;", message.attachment.contentType)
		attachment += fmt.Sprintf("name=\"%s\"\r\n", mime.BEncoding.Encode("UTF-8", fileTitle))
		buffer.WriteString(attachment)
		if err := mail.writeFile(buffer, message.attachment.name); err != nil {
			return err
		}
	}

	toAddress := mail.mergeSlice(message.to, message.cc)
	toAddress = mail.mergeSlice(toAddress, message.bcc)
	buffer.WriteString(fmt.Sprintf("\r\n--%s--", boundary))

	return smtp.SendMail(mail.host+":"+mail.port, mail.auth, message.from, toAddress, buffer.Bytes())
}

// mergeSlice 合并slice
func (mail SendMailCfg) mergeSlice(s1 []string, s2 []string) []string {
	slice := make([]string, len(s1)+len(s2))
	copy(slice, s1)
	copy(slice[len(s1):], s2)
	return slice
}

// writeHeader 拼接参数
func (mail SendMailCfg) writeHeader(buffer *bytes.Buffer, Header map[string]string) string {
	header := ""
	for key, value := range Header {
		header += key + ":" + value + "\r\n"
	}
	header += "\r\n"
	buffer.WriteString(header)
	return header
}

// writeFile 下载并写文件
func (mail SendMailCfg) writeFile(buffer *bytes.Buffer, fileName string) error {
	resp, err := http.Get(fileName)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	file, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	payload := make([]byte, base64.StdEncoding.EncodedLen(len(file)))
	base64.StdEncoding.Encode(payload, file)
	buffer.WriteString("\r\n")
	for index, line := 0, len(payload); index < line; index++ {
		buffer.WriteByte(payload[index])
		if (index+1)%76 == 0 {
			buffer.WriteString("\r\n")
		}
	}

	return nil
}

// loginAuth 认证
type loginAuth struct {
	username, password string
}

func LoginAuth(username, password string) smtp.Auth {
	return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
	// return "LOGIN", []byte{}, nil
	return "LOGIN", []byte(a.username), nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
	if more {
		switch string(fromServer) {
		case "Username:":
			return []byte(a.username), nil
		case "Password:":
			return []byte(a.password), nil
		}
	}
	return nil, nil
}
